﻿using System;
using System.Windows;
using System.Collections.Generic;
using System.Windows.Controls;
using System.Windows.Media;
using Bling.DSL;
using Bling.WPF;
using Bling.WPF.Util;
using Bling.Core;
using Bling.Util;
using Bling.Physics.UI;
using Bling.Physics;
using Bling.Properties;
using Bling.Mixins;
using Bling.Blocks;
using Bling.Shapes;

namespace Bling.Physics.Example {

  public static class FanBladeExtensions {
    /// <summary>
    /// If this blade is pinned or it is touching a previous blade that is pinned.
    /// </summary>
    public static BoolBl MaxPinned<BLOCK>(this FanA.BaseBlades<BLOCK>.IElemB body) where BLOCK : FanA.BaseBlades<BLOCK> {
      return FanA.BaseBlades<BLOCK>.MaxPinnedProperty[body.AsElem];
    }
    /// <summary>
    /// If this blade is pinnned or it is touching a next blade that is pinned.
    /// </summary>
    public static BoolBl MinPinned<BLOCK>(this FanA.BaseBlades<BLOCK>.IElemB body) where BLOCK : FanA.BaseBlades<BLOCK> {
      return FanA.BaseBlades<BLOCK>.MinPinnedProperty[body.AsElem];
    }
  }

  public class FanA : PhysicsCanvas {
    /// <summary>
    /// Basic fan blades.
    /// </summary>
    public abstract class BaseBlades<BLADES> : 
      TopBlock<BLADES, ImageBl>,       // mandatory base class for physics driven elements. 
      HasShape.IHas<BLADES>,           // activate shape matching
      MouseInput.IHas<BLADES, ImageBl> // support mouse input.
      where BLADES : BaseBlades<BLADES> {

      // custom per-blade properties.
      public static readonly Property<Block<BLADES>.ElemB, BoolBl> MaxPinnedProperty =
        Block<BLADES>.NewElementProperty<BoolBl>("NextPinned");
      public static readonly Property<Block<BLADES>.ElemB, BoolBl> MinPinnedProperty =
        Block<BLADES>.NewElementProperty<BoolBl>("PrevPinned");

      // image for each blade.
      protected abstract ImageSourceBl SourceFor(IntBl Idx);
      protected virtual ImageSourceBl PressedSourceFor(IntBl Idx) { return SourceFor(Idx); }

      public BaseBlades(BlockManager Manager, CanvasBl Canvas, int Count)
        : base(Manager, Count, (Block,idx) => {
          // each blade is an image.
          var ret = new ImageBl(Canvas) {
            ZIndex = Count - idx, // blades overlap each other in reverse order (first one on top)
            EnableAtRotation = Block.SourceFor(idx).Size / 2d, // bindable rotation necessary for shape matching. 
          };
          ret.RenderTransform.Scale.Bind = ret.IsMouseOver.Condition(1.1d, 1d);
          return ret;
        }) {
        // set the shape matching size.
        this.HasShapeE().SetSize(SourceFor(0).Size);
        this.HasShapeE().Scale = (Idx) => this[Idx].At.IsMouseOver.Condition(1.1d, 1d);

        var r = new Random();

        ForAll = body => {
          body.Particles().ForAll = particle => {
            particle.Position.Init =  
              new PointBl(r * 1000d.Bl() * particle.Index % 2, r * 1000d.Bl() * particle.Index / 2);
            particle.Position.InstallVerletStep(.1);
          };
          body.At.AtRotation = body.Rotation().AsDegrees;
          body.At.CenterPosition = body.Center();
          body.At.Source = body.IsPinned().Condition(SourceFor(body.Index), PressedSourceFor(body.Index));
        };
        this.HasShapeE().AddRelaxFirst = (block) => {
          block.ForAll = body => {
            body.Particles().ForAll = particle => {
              particle.Position.Relax[.1, (particle.Index % 3) == 0] = particle.Position.Clamp(Canvas.Size);
            };
          };
        };
        this.HasShapeE().AddRelaxCenter = (block) => {
          block.ForAll = body => {


            var next = body.Next[1];
            var prev = body.Prev[1];


            var atMax = Weight(body, body.MaxPinned());
            var prMax = Weight(prev, prev.MaxPinned());
            var atMin = Weight(body, body.MinPinned());
            var nxMin = Weight(next, next.MinPinned());

            var rMax = (prMax / (prMax + atMin));
            var rMin = (nxMin / (nxMin + atMax));

            //rMax = 1d;
            //rMin = 1d;

            var bodyA = body.Rotation().AsRadians;
            var nextA = next.Rotation().AsRadians;
            var prevA = prev.Rotation().AsRadians;
            var centA = body.Next[Count / 2].Rotation().AsRadians;


            var bodyP = (bodyA - centA).Normalize;
             var maxP = (prevA - centA).Normalize;
            var minP =  (nextA - centA).Normalize;

            maxP += OpenAngle;
            maxP = maxP.Max(bodyP);
            minP -= OpenAngle;
            minP = minP.Min(bodyP);

            var maxT = (maxP + centA).Normalize;
            var minT = (minP + centA).Normalize;
            var bodT = (bodyP + centA).Normalize;
            var lerp = .5d.Bl(); // (UseOpenState >= 1).Condition(.5, .1);
            {
              body.Rotation().Bind = Angle2DBl.SafeLerp(bodT, maxT, lerp * rMax).AsVec;
              body.Rotation().Bind = Angle2DBl.SafeLerp(bodT, minT, lerp * rMin).AsVec;

              body.MaxPinned().Bind = body.IsPinned() | ((maxP != bodyP) & prev.MaxPinned());
              body.MinPinned().Bind = body.IsPinned() | ((minP != bodyP) & next.MinPinned());
            }
          };

        };
      }
      public virtual Angle2DBl OpenAngle { get { return .05d.PI(); } }
      protected virtual DoubleBl Weight(ElemC body, BoolBl wt) {
        return (body.IsPinned()).Condition(1d + (1d - BaseStiffness) * 100d, 1);
        //return (!wt).Condition(1, 99);
      }
      public DoubleBl BaseStiffness { get { return .1d; } }

    }
    public class CategoryBlades : BaseBlades<CategoryBlades> {
      public static readonly Property<CategoryBlades, PointBl> PivotProperty =
        Block<CategoryBlades>.NewBlockProperty<PointBl>("Pivot");
      public PointBl Pivot {
        get { return PivotProperty[this]; }
        set { Pivot.Bind = value; }
      }
      public static readonly Property<CategoryBlades, DoubleBl> RadiusProperty =
        Block<CategoryBlades>.NewBlockProperty<DoubleBl>("Radius");
      public DoubleBl Radius {
        get { return RadiusProperty[this]; }
        set { Radius.Bind = value; }
      }
      static CategoryBlades() {
        //HasShape.Mixin<CategoryBlades>.Weight = (idx) => (idx == 0 | idx == 3).Condition(2d, 1d);
      }

      public CategoryBlades(BlockManager Manager, CanvasBl Canvas) : base(Manager, Canvas, 10) {
        this.Radius.Init = 20d;
        this.HasShapeE().RelaxCount = 1;
        this.HasShapeE().AddRelaxFirst = (block) => {
          PointBl Total = 0d;
          DoubleBl TotalWeight = 0d;
          BoolBl AnyPinned = false;
          for (int i = 0; i < block.Count; i++) {
            var body = block[i];
            AnyPinned = AnyPinned | body.IsPinned();
            DoubleBl Weight = (body.At.IsMouseCaptured.Condition(100d, 1d));
            var UseCenter = body.Particles()[0].Position + body.Particles()[3].Position;
            UseCenter = UseCenter / 2d;
            //Total += UseCenter * Weight;
            Total +=
              (UseCenter - body.Rotation().ToPoint * Radius) * Weight;
            TotalWeight += Weight;
          }
          Total = Total / TotalWeight;
          Pivot.Relax[AnyPinned.Condition(1d, .8d)] = Total;
          block.ForAll = (body) => {
            var NowCenter = (body.Particles()[0].Position + body.Particles()[3].Position) / 2d;
            var TargetCenter = block.Pivot + body.Rotation().ToPoint * block.Radius;
            var Delta = TargetCenter - NowCenter;
            body.Particles().ForAll = (particle) => {
              var G = (particle.Index == 0 | particle.Index == 3);
              particle.Position.Relax[1d, G] += Delta;
            };
          };
        };

        //};
      }
      private ImageSourceBl SourceFor(IntBl Idx, string Postfix) {
        StringBl Name;

        Name = (Idx == 0).Condition("GUIDE_COVER", "Category_".Bl() + ((Idx - 1) % 9 + 1));
        Name = Name + "_" + Postfix;
        Name = "Resources/".Bl() + Name + ".png";

        return Name.Map<ImageSource, ImageSourceBl>(uri => {
          return
            new System.Windows.Media.Imaging.BitmapImage((uri).MakePackUri(typeof(CategoryBlades)));
        });
      }
      protected override ImageSourceBl SourceFor(IntBl Idx) {
        return SourceFor(Idx, "Normal");
      }
      protected override ImageSourceBl PressedSourceFor(IntBl Idx) {
        return SourceFor(Idx, "Pressed");
      }
      
    }

    protected override Action Init(CanvasBl MainCanvas, BlockManager Manager) { // executes before blocks are initialized.
      MainCanvas.Background = Brushes.White;
      var Categories = new CategoryBlades(Manager, MainCanvas);
      return () => { };
    }

  }
}